﻿#if UNITY_EDITOR && DEBUG_BEHAVIOUR_TREE
#define DEBUG_TREE
#endif
using Devil;
using GameToolkit.BehaviourTree;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

namespace GameToolkit.Editor
{
    public interface IWindowGraph
    {
        EditorWindow AttachedWindow { get; }
    }

    public interface IRuntimeBinding
    {
        void BindRuntime(BehaviourTreePlayer player);
        void UnbindRuntime();
    }


    public class BehaviourTreeGraph : GraphView, IWindowGraph, IDisposable, IGraphViewAutoConnector
#if DEBUG_TREE
        , IRuntimeBinding
#endif
    {
        readonly static string SerializationPrefix = "behaviourtree://";

        internal class GraphEdgeLoader
        {
            BehaviourTreeGraph mGraph;
            Dictionary<Port, HashSet<Port>> mConnected;
            IList<GraphElement> mAllNodes;
            internal GraphEdgeLoader(BehaviourTreeGraph graph, IList<GraphElement> allNodes)
            {
                mGraph = graph;
                mConnected = new Dictionary<Port, HashSet<Port>>();
                mAllNodes = allNodes;
            }

            public INodeWithComposite GetNode(Composite composite)
            {
                if (composite == null)
                    return null;
                for (int i = 0; i < mAllNodes.Count; i++)
                {
                    if (mAllNodes[i] is INodeWithComposite node && node.composite == composite)
                        return node;
                }
                return null;
            }

            public T NewEdge<T>(Port input, Port output) where T : Edge, new()
            {
                if (input == null || output == null)
                    return null;
                HashSet<Port> outputs;
                if (!mConnected.TryGetValue(input, out outputs))
                {
                    outputs = new HashSet<Port>();
                    mConnected.Add(input, outputs);
                }
                if (outputs.Add(output))
                {
                    var edge = new T();
                    edge.input = input;
                    edge.output = output;
                    input.Connect(edge);
                    output.Connect(edge);
                    mGraph.AddElement(edge);
                    return edge;
                }
                return null;
            }
        }

        public class UxmlFac : UxmlFactory<BehaviourTreeGraph, UxmlTraits> { }

        public event Action<BehaviourTreeGraph> OnIsDirty;

        public event Action<BehaviourTreeGraph> OnRegisterDirty;

        BehaviourTreePlayer mPlayer;
        public BehaviourTreePlayer BindingPlayer => mPlayer;
        RootNode mRoot;
        ContentZoomer mZoomer;
        RectangleSelector mRectSelector;
        Label mScale;
        Label mDescription;
        Label mDebugInfo;
        List<Port> mCompatiblePorts = new List<Port>();
        bool mIsDirty;
        bool mReopen;
        // 上下文菜单鼠标位置
        Vector2 mMouseContextPosition;
        BehaviourTreeAsset mAsset;
        public EditorWindow AttachedWindow { get; internal set; }
        public BehaviourTreeAsset asset
        {
            get { return mAsset; }
            set
            {
                //var clean = mAsset == null && ((object)mAsset) != null;
                //if (mAsset != value || clean)
                //{
                    mReopen = true;
                    mAsset = value;
                    if (mInspectorToggle != null)
                        mInspectorToggle.value = value != null && value.m_ShowInspector;
                    LoadGraphNodes();
                //}
            }
        }

        BehaviourNodeInspector mInspector;
        Toggle mInspectorToggle;

        public bool IsDirty
        {
            get { return mIsDirty; }
            set
            {
                if(mIsDirty != value)
                {
                    mIsDirty = value;
                    OnIsDirty?.Invoke(this);
                }
            }
        }

        internal Toggle InspectorToggle
        {
            get { return mInspectorToggle; }
            set
            {
                if (mInspectorToggle != value)
                {
                    if (mInspectorToggle != null)
                        mInspectorToggle.UnregisterValueChangedCallback(OnToggleInspector);
                    mInspectorToggle = value;
                    if (mInspectorToggle != null)
                    {
                        if (mAsset != null)
                            mInspectorToggle.value = mAsset.m_ShowInspector;
                        mInspectorToggle.RegisterValueChangedCallback(OnToggleInspector);
                        mInspector.visible = ShouldInspectorVisible();
                    }
                }
            }
        }

        bool ShouldInspectorVisible()
        {
            return mInspectorToggle != null && mInspectorToggle.value /*&& mInspector.TargetCount > 0*/;
        }

        bool mReselect;
        // 通知
        GUIContent mNtf;
        internal RootNode Root => mRoot;
        // 将创建的节点连接至
        AutoCreateFilter mAutoCreateFilter;
        // 当前装饰节点
        internal readonly HashSet<ExecutableNodeVisual> mCurrentDecorationNodes = new HashSet<ExecutableNodeVisual>();

        public bool IsEditable => mPlayer == null && !EditorApplication.isPlayingOrWillChangePlaymode;
        public override bool supportsWindowedBlackboard => true;

        protected override bool canDeleteSelection => IsEditable ? base.canDeleteSelection : false;
        protected override bool canCopySelection => IsEditable ? base.canCopySelection : false;
        protected override bool canPaste => IsEditable ? base.canPaste : false;
        protected override bool canCutSelection => IsEditable ? base.canCutSelection : false;
        protected override bool canDuplicateSelection => IsEditable ? base.canDuplicateSelection : false;

        public override Blackboard GetBlackboard()
        {
            return mInspector;
        }

        public BehaviourTreeGraph() : base()
        {
            mNtf = new GUIContent();
            var view = contentContainer;
            if(view != null)
            {
                view.name = "Viewport";
            }

            name = "behaviour-tree-graph";

            var gap = new Length(10, LengthUnit.Pixel);

            mScale = new Label();
            mScale.name = "graph-scale";
            mScale.pickingMode = PickingMode.Ignore;
            hierarchy.Insert(0, mScale);

            mDebugInfo = new Label();
            mDebugInfo.name = "debug-info";
            mDebugInfo.pickingMode = PickingMode.Ignore;
            hierarchy.Insert(0, mDebugInfo);
#if DEBUG_TREE
            mDebugInfo.text = "undefine symbol \"DEBUG_BEHAVIOUR_TREE\" to {Edit Mode}";
#else
            mDebugInfo.text = "define symbol \"DEBUG_BEHAVIOUR_TREE\" to {Debug Mode}";
#endif

            mDescription = new Label();
            mDescription.name = "graph-desc";
            mDescription.pickingMode = PickingMode.Ignore;
            mDescription.text = "Asset Info";
            //mDescription.text = "asset path";
            hierarchy.Insert(0, mDescription);

            var grid = new GridBackground();
            grid.name = "graph-grid";
            hierarchy.Insert(0, grid);

            var clampWindow = new VisualElement();
            //clampWindow.SetEnabled(false);
            clampWindow.pickingMode = PickingMode.Ignore;
            clampWindow.name = "clamp-window";
            hierarchy.Add(clampWindow);

            mRoot = new RootNode();
            AddElement(mRoot);
            mRoot.SetPosition(new Rect(20, 200, 1, 1));

            //var reg = new RegisterNodeVisual(null, null);
            //AddElement(reg);
            //reg.SetPosition(new Rect(400, 200, 1, 1));

            var styles = BehaviourTreeGraphUtils.GetStyleSheet();
            //AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.devil.gametoolkit/Editor/USS/GraphStyles.uss");
            if (styles != null)
                styleSheets.Add(styles);

            mInspector = new BehaviourNodeInspector(this);
            clampWindow.Add(mInspector);

            nodeCreationRequest = OnCreateNode;
            viewTransformChanged = OnTransformChanged;
            graphViewChanged = OnGraphViewChanged;
            serializeGraphElements = OnSerializeGraphElement;
            unserializeAndPaste = OnDeserializeGraphElement;
            canPasteSerializedData = CanPasteGraphElement;

            mZoomer = new ContentZoomer();
            mZoomer.maxScale = 3f;
            mZoomer.minScale = 0.2f;

            mRectSelector = new RectangleSelector();

            this.AddManipulator(new SelectionDragger());
            this.AddManipulator(new ContentDragger());

            this.AddManipulator(mRectSelector);
            this.AddManipulator(mZoomer);

            this.RegisterCallback<MouseMoveEvent>(OnMouseMove);
            this.RegisterCallback<MouseDownEvent>(OnMouseDown);

            OnTransformChanged(this);

            //var arrow = new Arrow();
            //arrow.layer = -1;
            //arrow.SetPosition(new Rect(100, 250, 130, 20));
            //AddElement(arrow);
        }

        internal void Notice(string msg, float duration = 2f)
        {
            if (AttachedWindow != null && !string.IsNullOrEmpty(msg))
            {
                mNtf.text = msg;
                AttachedWindow.ShowNotification(mNtf, duration);
            }
        }

        private void OnMouseDown(MouseDownEvent evt)
        {
            mMouseContextPosition = evt.localMousePosition;
        }

        private void OnMouseMove(MouseMoveEvent evt)
        {
            mMouseContextPosition = evt.localMousePosition;
        }

        internal void Relayout()
        {
            var reselect = mReopen || mReselect;
            mReselect = false;
            if (mReopen)
            {
                mReopen = false;
                FrameAll();
            }
            if (reselect)
                mInspector.GetSelections();
            mInspector.visible = ShouldInspectorVisible();// mInspector.TargetCount > 0 && mInspectorToggle != null && mInspectorToggle.value;
        }

        private void OnToggleInspector(ChangeEvent<bool> evt)
        {
            if (mAsset != null)
                mAsset.m_ShowInspector = evt.newValue;
            if (mInspector != null)
                mInspector.visible = ShouldInspectorVisible();// mInspector.TargetCount > 0 && evt.newValue;
        }

        public void Dispose()
        {
            InspectorToggle = null;
            IDisposable dis;
            dis = mInspector;
            if (dis != null)
                dis.Dispose();
            foreach (var nod in nodes)
            {
                (nod as IDisposable)?.Dispose();
            }
        }

#if DEBUG_TREE
        public void BindRuntime(BehaviourTreePlayer player)
        {
            if (player == null || !player.IsValid || player.BindingAsset == null)
                player = null;
            var hasPlayer = mPlayer != null;
            if (player != mPlayer)
            {
                UnbindRuntime(false);
                mPlayer = player;
                if (player != null)
                {
                    asset = player.BindingAsset;
                    var flags = ~(Capabilities.Movable | Capabilities.Copiable | Capabilities.Deletable);
                    foreach (var node in nodes)
                    {
                        node.capabilities &= flags;
                        if (node is IRuntimeBinding bind)
                            bind.BindRuntime(player);
                    }
                    player.OnSubmitBehaviourState += SubmitBehaviourState;
                    this.RemoveManipulator(mRectSelector);
                }
                SubmitBehaviourState(player);
                if (mPlayer != null && !hasPlayer)
                {
                    Notice("已绑定行为树播放器");
                }
            }
        }

        public void UnbindRuntime()
        {
            UnbindRuntime(true);
        }

        void UnbindRuntime(bool submit)
        {
            if (mPlayer != null)
            {
                mPlayer.OnSubmitBehaviourState -= SubmitBehaviourState;
                mPlayer = null;
                var port = mRoot.outputContainer[0] as Port;
                port.portColor = ExecutableNodeVisual.STAT_COLOR[0];
                var flags = Capabilities.Movable | Capabilities.Copiable | Capabilities.Deletable;
                foreach (var node in nodes)
                {
                    if (node == mRoot)
                        node.capabilities |= Capabilities.Movable;
                    else
                        node.capabilities |= flags;
                    if (node is IRuntimeBinding bind)
                        bind.UnbindRuntime();
                }
                if (submit)
                {
                    SubmitBehaviourState(null);
                    this.AddManipulator(mRectSelector);
                }
            }
        }

        void SubmitBehaviourState(BehaviourTreePlayer player)
        {
            if(player == null)
            {
                var port = mRoot.outputContainer[0] as Port;
                port.portColor = ExecutableNodeVisual.STAT_COLOR[0];
                foreach (var edge in edges)
                {
                    var flow = edge as FlowEdge;
                    if (flow != null)
                    {
                        flow.speed = 0;
                    }
                    edge.UpdateEdgeControl();
                }
            }
            else if (player.IsValid)
            {
                var port = mRoot.outputContainer[0] as Port;
                port.portColor = ExecutableNodeVisual.STAT_COLOR[(int)player.State];
                foreach (var edge in edges)
                {
                    var flow = edge as FlowEdge;
                    if (flow != null)
                    {
                        var node = edge.input?.node as ExecutableNodeVisual;
                        if (node != null && node.RuntimeInstance != null && mPlayer.IsActiveNode(node.RuntimeInstance))
                            flow.speed = 200f;
                        else
                            flow.speed = 0;
                    }
                    edge.UpdateEdgeControl();
                }
            }
            else
            {
                var win = AttachedWindow as BehaviourTreeEditor;
                win?.ResetPlayer(null);
            }
        }

#endif

        public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
        {
            var win = AttachedWindow as BehaviourTreeEditor;
            var enableEdit = mPlayer == null && !EditorApplication.isPlayingOrWillChangePlaymode;
            if (win == null)
                return;
            mMouseContextPosition = evt.localMousePosition;
#if DEBUG_TREE
            evt.menu.AppendAction("Find And Debug AI", (menu) => {
                var player = BehaviourTreePlayer.FindPlayer(mAsset);
                if (player != null && AttachedWindow is BehaviourTreeEditor editor)
                    editor.ResetPlayer(player);
            });
#endif
            if (!enableEdit)
                return;
            //var evData = evt.triggerEvent as MouseUpEvent;
            if (evt.target is GraphView && mAsset != null)
            {
                evt.menu.AppendAction("Create New Behaviour Tree", (dma) => { if (win != null) win.behaviourTreeAsset = null; });
            }
            if (evt.target != mRoot)
            {
                base.BuildContextualMenu(evt);
                Func<DropdownMenuAction, DropdownMenuAction.Status> statFunc = (act) => DropdownMenuAction.Status.Normal;
                if (evt.target is ExecutableNodeVisual tnode)
                {
                    var t = evt.triggerEvent as MouseUpEvent;
                    Vector2 pos;
                    if (t == null)
                        pos = contentViewContainer.ChangeCoordinatesTo(AttachedWindow.rootVisualElement.parent, tnode.GetPosition().position) + AttachedWindow.position.position;
                    else
                        pos = t.mousePosition + AttachedWindow.position.position;
                    var data = new CreateNodeDecoratorContext(tnode, pos);
                    evt.menu.AppendAction("Add Node's Decorator", AddDecoratorNode, statFunc, data);
                }
            }
            int selNode = 0;
            foreach (var sel in selection)
            {
                if (sel is Node node && node.IsGroupable())
                    selNode++;
                if (selNode > 1)
                    break;
            }
            //evt.menu.AppendSeparator();
            evt.menu.AppendAction("Group Selections", GroupSelections, selNode > 1 ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
            evt.menu.AppendAction("Ungroup Selections", UngroupSelections, selNode > 0 ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
        }

        private void GroupSelections(DropdownMenuAction action)
        {
            var lst = new RandomList<Node>();
            foreach (var sel in selection)
            {
                if (sel is Node node && node.IsGroupable())
                    lst.Add(node);
            }
            UngroupNodes(lst);
            var grp = new Group();
            grp.title = "Group";
            foreach (var item in selection)
            {
                if (item is Node node && node.IsGroupable())
                    grp.AddElement(node);
            }
            if (grp.containedElements.Count() > 0)
            {
                this.AddElement(grp);
                IsDirty = true;
            }
        }

        private void UngroupSelections(DropdownMenuAction action)
        {
            var lst = new RandomList<Node>();
            foreach (var sel in selection)
            {
                if (sel is Node node)
                    lst.Add(node);
            }
            UngroupNodes(lst);
        }

        void UngroupNodes(RandomList<Node> lst)
        {
            var groups = this.Query<Group>();
            foreach (var group in groups.Build())
            {
                for (int i = lst.Count - 1; i >= 0; i--)
                {
                    if (group.ContainsElement(lst[i]))
                    {
                        group.RemoveElement(lst[i]);
                        lst.RemoveAt(i);
                    }
                }
            }
        }

        private void AddDecoratorNode(DropdownMenuAction action)
        {
            var context = action.userData as CreateNodeDecoratorContext;
            if (context == null)
                return;
            var win = AttachedWindow as BehaviourTreeEditor;
            if (win != null && EditorWindow.focusedWindow == win)
            {
                mCurrentDecorationNodes.Add(context.node);
                SearchWindow.Open(new SearchWindowContext(context.mousePosition), win.DecoratorProvidor);
            }
        }

        private bool CanPasteGraphElement(string data)
        {
            return !string.IsNullOrEmpty(data) && data.StartsWith(SerializationPrefix);
        }

        void Extend(ref Rect rect, in Rect incoming)
        {
            var x = Mathf.Min(rect.xMin, incoming.xMin);
            var y = Mathf.Min(rect.yMin, incoming.yMin);
            var z = Mathf.Max(rect.xMax, incoming.xMax);
            var w = Mathf.Max(rect.yMax, incoming.yMax);
            rect = new Rect(x, y, z - x, w - y);
        }

        private string OnSerializeGraphElement(IEnumerable<GraphElement> elements)
        {
            if (AttachedWindow == null)
                return null;
            var nodes = new HashSet<Node>();
            var edges = new HashSet<Edge>();
            foreach (var it in elements)
            {
                if (it is INodeWithComposite ct && ct.composite != null && it is Node node)
                {
                    nodes.Add(node);
                }
                else if (it is Edge ed)
                {
                    edges.Add(ed);
                }
            }
            if (nodes.Count == 0)
                return null;
            var buf = ParallelUtils.GetBuilder(SerializationPrefix);
            buf.Append(AttachedWindow.GetInstanceID().ToString("X")).Append('/');
            foreach (var node in nodes)
            {
                buf.Append(node.viewDataKey).Append(';');
            }
            if (edges.Count > 0)
            {
                buf.Append(';');
                foreach (var edge in edges)
                {
                    var t1 = edge.input.node;
                    var t2 = edge.output.node;
                    if (t1 != null && t2 != null && nodes.Contains(t1) && nodes.Contains(t2))
                    {
                        buf.Append(edge.viewDataKey).Append(';');
                    }
                }
            }
            return ParallelUtils.ReturnRelease(buf);
        }

        private void OnDeserializeGraphElement(string operationName, string data)
        {
            if (operationName == "Paste" || operationName == "Duplicate" && data.StartsWith(SerializationPrefix))
            {
                var startIndex = SerializationPrefix.Length;
                var index = data.IndexOf('/', startIndex);
                if (index <= startIndex)
                    return;
                var content = data.Substring(index + 1);
                if (string.IsNullOrEmpty(content))
                    return;
                var id = int.Parse(data.Substring(startIndex, index - startIndex), System.Globalization.NumberStyles.HexNumber);
                var win = EditorUtility.InstanceIDToObject(id) as BehaviourTreeEditor;
                if (win == null || win.Graph == null)
                {
                    Notice("数据源已关闭，无法完成复制。");
                    return;
                }
                var tnodes = new HashSet<Node>();
                var tedges = new HashSet<Edge>();
                var isNode = true;
                var lst = content.Split(';');
                Rect rect = default;
                VisualElement coord = null;
                // get nodes & edges
                for (int i = 0; i < lst.Length; i++)
                {
                    if (string.IsNullOrEmpty(lst[i]))
                    {
                        isNode = false;
                        continue;
                    }
                    else if (isNode)
                    {
                        foreach (var node in win.Graph.nodes)
                        {
                            if (node.viewDataKey == lst[i])
                            {
                                if (coord == null)
                                {
                                    rect = node.GetPosition();
                                    coord = node.parent;
                                }
                                else
                                {
                                    Extend(ref rect, node.GetPosition());
                                }
                                tnodes.Add(node);
                                break;
                            }
                        }
                    }
                    else
                    {
                        foreach (var edge in win.Graph.edges)
                        {
                            if (edge.viewDataKey == lst[i])
                            {
                                tedges.Add(edge);
                                break;
                            }
                        }
                    }
                }
                //Debug.Log($"operation: {operationName}\n  data: {data}\n mouse: {mMouseContextPosition}\n nodes: {tnodes.Count}\n edges: {tedges.Count}");
                var offset = this.ChangeCoordinatesTo(coord, mMouseContextPosition) - rect.center;
                Dictionary<Node, Node> copyMap = new Dictionary<Node, Node>();
                ClearSelection();
                foreach(var node in tnodes)
                {
                    var cnode = node as INodeWithComposite;
                    if (cnode == null)
                        continue;
                    var c = JsonSerializedObject.Clone(cnode.composite, true) as Composite;
                    if (c != null)
                    {
                        c.ResetGUID();
                        c.m_Position = node.GetPosition().position + offset;
                        var copynode = cnode.compositeInfo.CreateGraphNode(c, c.m_Position);
                        if (copynode != null)
                        {
                            copyMap[node] = copynode;
                            AddElement(copynode);
                            AddToSelection(copynode);
                        }
                    }
                }
                foreach (var edge in tedges)
                {
                    Port input = null, output = null;
                    Node tnode;
                    if (copyMap.TryGetValue(edge.input.node, out tnode))
                    {
                        var portId = ((INodeWithComposite)edge.input.node).GetInputPortId(edge);
                        input = ((INodeWithComposite)tnode).GetInputPort(portId);
                    }
                    if (copyMap.TryGetValue(edge.output.node, out tnode))
                    {
                        var portId = ((INodeWithComposite)edge.output.node).GetOutputPortId(edge);
                        output = ((INodeWithComposite)tnode).GetOutputPort(portId);
                    }
                    if (input != null && output != null)
                    {
                        Edge newedge;
                        if (input.node is ExecutableNodeVisual && output.node is ExecutableNodeVisual)
                            newedge = NewEdge<FlowEdge>(input, output);
                        else
                            newedge = NewEdge<LinkEdge>(input, output);
                        if (newedge != null)
                            AddToSelection(newedge);
                    }
                }
                if (tnodes.Count > 0)
                    IsDirty = true;
                Notice($"{operationName} (Nodes +{tnodes.Count})");
            }
        }

        private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
        {
            var changeData = graphViewChange;
            if (changeData.elementsToRemove == null)
            {
                changeData.elementsToRemove = new List<GraphElement>();
            }
            var delNum = changeData.elementsToRemove.Count;
            var edgeNum = changeData.edgesToCreate == null ? 0 : changeData.edgesToCreate.Count;
            var movNum = changeData.movedElements == null ? 0 : changeData.movedElements.Count;
            var dirty = delNum > 0 || edgeNum > 0 || movNum > 0;
            GraphElement toRemov = null;
            for (int i = 0; i < edgeNum; i++)
            {
                var link = changeData.edgesToCreate[i] as LinkEdge;
                if (link == null)
                    continue;
                var from = link.input;
                var to = link.output?.node;
                if (from == null || to == null)
                    continue;
                foreach (var edge in edges)
                {
                    if (edge == link)
                        continue;
                    var from2 = edge.input;
                    var to2 = edge.output?.node;
                    if (from2 == from && to2 == to)
                    {
                        toRemov = edge;
                        if (!changeData.elementsToRemove.Contains(edge))
                            changeData.elementsToRemove.Add(edge);
                        break;
                    }
                }
            }
            var removedRegister = new HashSet<GraphElement>();
            for (int i = 0; i < delNum; i++)
            {
                if (changeData.elementsToRemove[i] is TreeGraphNode tnode)
                    tnode.NotifyRemoved();
                if (changeData.elementsToRemove[i] is INodeWithComposite cnode && cnode.composite is RegisterComposite)
                {
                    removedRegister.Add(changeData.elementsToRemove[i]);
                }
            }

            if (toRemov != null)
                RemoveElement(toRemov);
            if (removedRegister.Count > 0)
            {
                foreach (var node in nodes)
                {
                    if (node is RegisterNodeVisual reg && removedRegister.Contains(reg.ReferenceTo) && !changeData.elementsToRemove.Contains(node))
                    {
                        changeData.elementsToRemove.Add(node);
                        foreach (var edge in reg.GetOutputPort(0).connections)
                        {
                            changeData.elementsToRemove.Add(edge);
                        }
                    }
                }
                OnRegisterDirty?.Invoke(this);
            }

            if (mAsset != null)
            {
                IsDirty |= dirty;
            }
            return changeData;
        }

        public override void AddToSelection(ISelectable selectable)
        {
            base.AddToSelection(selectable);
            mReselect = true;
        }

        public override void RemoveFromSelection(ISelectable selectable)
        {
            base.RemoveFromSelection(selectable);
            mReselect = true;
        }

        public override void ClearSelection()
        {
            base.ClearSelection();
            mReselect = true;
        }


        private void OnTransformChanged(GraphView graphView)
        {
            var scale = graphView.viewTransform.scale;
            mScale.text = string.Format("[1 : {0:0.0}]", scale.x);
        }


        private void OnCreateNode(NodeCreationContext context)
        {
            if (mPlayer != null || EditorApplication.isPlayingOrWillChangePlaymode)
                return;
            var win = AttachedWindow as BehaviourTreeEditor;
            if (win != null && EditorWindow.focusedWindow == AttachedWindow)
            {
                mAutoCreateFilter = null;
                var providor = win.NodeProvidor;
                providor.Filter = null;
                SearchWindow.Open(new SearchWindowContext(context.screenMousePosition, 260), providor);
            }
        }

        public void CreateNode(AutoCreateFilter filter, Vector2 pos)
        {
            if (mPlayer != null || EditorApplication.isPlayingOrWillChangePlaymode)
                return;
            var win = AttachedWindow as BehaviourTreeEditor;
            if (win != null && EditorWindow.focusedWindow == AttachedWindow)
            {
                mAutoCreateFilter = filter;
                mCurrentDecorationNodes.Clear();
                var providor = win.NodeProvidor;
                providor.Filter = mAutoCreateFilter == null ? null : mAutoCreateFilter.nodeFilter;
                var root = win.rootVisualElement;
                Vector2 screenMousePosition = this.ChangeCoordinatesTo(root, pos) + win.position.position;
                SearchWindow.Open(new SearchWindowContext(screenMousePosition, 260), providor);
            }
        }

        internal bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context, bool asDecorator)
        {
            var connect = mAutoCreateFilter;
            mAutoCreateFilter = null;
            var windowRoot = AttachedWindow.rootVisualElement;
            var nodePos = windowRoot.parent.ChangeCoordinatesTo(contentViewContainer, context.screenMousePosition - AttachedWindow.position.position);

            if (entry.userData is RegisterNodeVisual reg && reg.composite is RegisterComposite composite)
            {
                var register = new RegisterReference();
                register._guid = GUID.Generate().ToString();
                register.m_Position = nodePos;
                register.Register = composite;
                var node = new RegisterNodeVisual(BehaviourTreeLib.Instance.GetCompositeInfo(register), register);
                node.ReferenceTo = reg;
                AddElement(node);

                if(connect != null && connect.autoConnectPort != null)
                {
                    node.AutoConnectOnCreate(this, connect.autoConnectPort);
                }
                IsDirty = true;
                return true;
            }
            else if (entry.userData is BehaviourTreeLib.CompositeInfo info)
            {

                if (asDecorator)
                {
                    if (mCurrentDecorationNodes.Count == 0)
                        return false;
                    foreach (var item in mCurrentDecorationNodes)
                    {
                        var decor = info.WrapType.Create() as Composite;
                        if (decor == null)
                            continue;
                        if (item is ExecutableNodeVisual visual)
                            visual.AddDecorator(decor);
                    }
                    mCurrentDecorationNodes.Clear();
                    mInspector.GetSelections(true);
                    IsDirty = true;
                    return true;
                }

                
                //Composite nodeContext = default;
                //nodeContext.m_Position = nodePos;
                var node = info.CreateGraphNode(null, nodePos);
                if (node != null)
                {
                    this.AddElement(node);
                    if (connect != null && connect.autoConnectPort != null && node is INodeWithComposite cnode)
                    {
                        cnode.AutoConnectOnCreate(this, connect.autoConnectPort);
                    }

                    IsDirty = true;
                    if (node is INodeWithComposite com && com.composite is RegisterComposite)
                        OnRegisterDirty?.Invoke(this);
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        GraphElement GetGraphNode(IList<GraphElement> nodes, Composite data)
        {
            if (data == null)
                return null;
            for (int i = 0; i < nodes.Count; i++)
            {
                var nd = nodes[i] as INodeWithComposite;
                if (nd != null && nd.composite == data)
                    return nodes[i];
            }
            return null;
        }

        internal Edge NewEdge<T>(Port input, Port output) where T : Edge, new()
        {
            if (input == null || output == null || input.node == output.node)
                return null;
            var edge = new T();
            edge.input = input;
            edge.output = output;
            input.Connect(edge);
            output.Connect(edge);
            AddElement(edge);
            return edge;
        }

        internal void LoadGraphNodes()
        {
            var graphnodes = new List<GraphElement>();
            foreach(var item in graphElements)
            {
                if (item != mRoot)
                    graphnodes.Add(item);
            }
            DeleteElements(graphnodes);
            var dirty = false;
            if (mAsset != null)
            {
                var path = AssetDatabase.GetAssetPath(mAsset);
                if (string.IsNullOrEmpty(path))
                    path = $"{mAsset.name} ({mAsset.GetInstanceID().ToString("XX")})";
#if DEBUG_TREE
                path = $"{{Debug}} {path}";
#else
                path = $"{{Edit}} {path}";
#endif
                mDescription.text = path;
                // create nodes
                graphnodes.Clear();
                var size = mAsset.m_Nodes == null? 0: mAsset.m_Nodes.Length;
                for (int i = 0; i < size; i++)
                {
                    var btnode = mAsset.m_Nodes[i];
                    Node node = null;
                    var composite = btnode == null ? null : BehaviourTreeLib.Instance.GetCompositeInfo(btnode);
                    if (composite != null)
                    {
                        node = composite.CreateGraphNode(btnode, Vector2.zero);
                    }
                    if (node != null)
                    {
                        if (string.IsNullOrEmpty(btnode._guid))
                        {
                            btnode._guid = GUID.Generate().ToString();
                            dirty = true;
                        }
                        graphnodes.Add(node);
                        AddElement(node);
                    }
                }
                var edgeLoader = new GraphEdgeLoader(this, graphnodes);
                // remove invalid register
                for (int i = graphnodes.Count - 1; i >= 0; i--)
                {
                    var it = graphnodes[i] as RegisterNodeVisual;
                    if (it != null && !it.GetReferenceTo(this))
                    {
                        graphnodes.RemoveAt(i);
                        RemoveElement(it);
                    }
                }
                // connect nodes
                for (int i = 0; i < graphnodes.Count; i++)
                {
                    if (graphnodes[i] is INodeWithComposite cnode)
                    {
                        cnode.LoadCompositeConnections(edgeLoader);
                    }
                }
                var root = mAsset.m_RootIndex;
                if (root < graphnodes.Count && root >= 0)
                {
                    mRoot.SetPosition(new Rect(mAsset.m_RootPosition, Vector2.zero));
                    var node = graphnodes[root] as ExecutableNodeVisual;
                    if (node != null)
                    {
                        var edge = new FlowEdge();
                        edge.input = node.startPort;
                        edge.output = mRoot.actionPort;
                        edge.input.Connect(edge);
                        edge.output.Connect(edge);
                        AddElement(edge);
                    }
                }
                // create groups
                size = mAsset.m_Groups == null ? 0 : mAsset.m_Groups.Length;
                for (int i = 0; i < size; i++)
                {
                    var grp = mAsset.m_Groups[i];
                    var item = new Group();
                    item.title = grp.title;
                    var len = grp.composites == null ? 0 : grp.composites.Length;
                    for (int k = 0; k < len; k++)
                    {
                        var node = GetGraphNode(graphnodes, grp.composites[k]);
                        if (node != null)
                            item.AddElement(node);
                    }
                    if (item.containedElements.Count() > 0)
                        AddElement(item);
                }
            }
            else
            {
                mDescription.text = "<none>";
            }
            //ParallelUtils.Schedule(() => FrameAll(), true);
            IsDirty = dirty;
            OnRegisterDirty?.Invoke(this);
        }


        internal void SaveGraph(bool saveAs)
        {
            bool saved = false;
            if (mAsset != null && !saveAs)
            {
                saved = true;
                this.Save(mAsset);
                IsDirty = false;
            }
            else
            {
                var path = EditorUtility.SaveFilePanelInProject("Save BehaviourTree", "BehaviourTree", "behaviourtree", "Save BehaviourTreeAsset");
                if (!string.IsNullOrEmpty(path))
                {
                    saved = true;
                    var asset = ScriptableObject.CreateInstance<BehaviourTreeAsset>();
                    asset.name = NativeFileSystem.GetFileName(path, true);
                    this.Save(asset);
                    var json = JsonSerializedObject.ExportFrom(asset);
                    NativeFileSystem.WriteAllText(path, json.ToJson(), Encoding.UTF8);
                    AssetDatabase.ImportAsset(path);
                    var inst = AssetDatabase.LoadAssetAtPath<BehaviourTreeAsset>(path);
                    if (inst != asset && inst != null)
                    {
                        GlobalUtil.Destroy(asset);
                    }
                    else
                    {
                        inst = asset;
                    }
                    ((BehaviourTreeEditor)AttachedWindow).behaviourTreeAsset = inst;
                    mReopen = false;
                }
            }
            if(saved && AttachedWindow != null)
            {
                mNtf.text = "已保存!";
                AttachedWindow.ShowNotification(mNtf, 1f);
            }
        }

        bool IsChildOf(Node child, Node parent, System.Type linkDataType)
        {
            var lst = new List<Node>();
            lst.Add(child);
            var index = 0;
            while (index < lst.Count)
            {
                var item = lst[index++];
                var ports = item.Query<Port>();
                foreach (var port in ports.Build())
                {
                    if (port.direction != Direction.Input || port.portType != linkDataType)
                        continue;
                    var edges = port.connections;
                    if (edges == null)
                        continue;
                    foreach (var edge in edges)
                    {
                        if (edge.output.node == parent)
                            return true;
                        else if (!lst.Contains(edge.output.node))
                            lst.Add(edge.output.node);
                    }
                }
            }
            return false;
        }

        bool IsCompatiblePort(Port startPort, Port port)
        {
            if (port.direction == startPort.direction || startPort.node == port.node || port.portType != startPort.portType)
                return false;
            if(startPort.node is INodeWithComposite snode)
            {
                if (!snode.IsCompatiblePort(port))
                    return false;
            }
            else if(port.node is INodeWithComposite cnode)
            {
                if (!cnode.IsCompatiblePort(startPort))
                    return false;
            }

            if (startPort.direction == Direction.Input && IsChildOf(port.node, startPort.node, port.portType))
                return false;
            else if (startPort.direction == Direction.Output && IsChildOf(startPort.node, port.node, port.portType))
                return false;
            return true;
        }

        public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
        {
            mCompatiblePorts.Clear();
            if (mPlayer == null)
            {
                var startInfo = startPort.node as INodeWithComposite;
                foreach (var port in ports)
                {
                    if (IsCompatiblePort(startPort, port))
                        mCompatiblePorts.Add(port);
                }
            }
            return mCompatiblePorts;
            //return base.GetCompatiblePorts(startPort, nodeAdapter);
        }

        class CreateNodeDecoratorContext
        {
            public Vector2 mousePosition;
            public ExecutableNodeVisual node;
            public CreateNodeDecoratorContext(ExecutableNodeVisual node, Vector2 mousePos) {
                this.node = node;
                this.mousePosition = mousePos;
            }
        }

        internal class RootNode : Node
        {
            internal readonly Port actionPort;
            public RootNode() : base()
            {
                name = "behaviour-tree-root";
                title = "BEHAVIOUR ROOT";

                var icon = new VisualElement();
                icon.name = "icon";
                icon.AddToClassList("node-icon");
                titleContainer.Insert(0, icon);

                actionPort = InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Composite));
                actionPort.portName = "START";
                actionPort.portColor = ExecutableNodeVisual.STAT_COLOR[0];//  Color.green;
                outputContainer.Add(actionPort);

                var content = new VisualElement();
                content.name = "node-container";
                content.AddToClassList("node-contents");
                mainContainer.Add(content);

                var desc = new Label("The behaviour tree start here");
                desc.AddToClassList("node-descript");
                content.Add(desc);

                capabilities &= ~Capabilities.Deletable;
            }

            public override Port InstantiatePort(Orientation orientation, Direction direction, Port.Capacity capacity, Type type)
            {
                return Port.Create<FlowEdge>(orientation, direction, capacity, type);
            }

        }

        

    }

}
